﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Net;
using System.IO.Compression;

namespace ConsoleApplication
{
    internal class IMDbDictionary
    {
        const string plotUrl = "ftp://ftp.fu-berlin.de/pub/misc/movies/database/plot.list.gz";
        const string plotCacheName = "plot.list.cache.gz";

        private ManualResetEvent downloadSync;

        public struct Movie
        {
            public string Title;
            public string Plot;
            public string SearchTitle;
        }

        private long _bytesRecieved;
        private long _bytesTotal;

        public enum IMDbEventType 
        {
            CacheOld,
            CacheBuildComplete,
            CacheBuildStart,
            CacheBuildProgress,
            RequestingCache,
            RequestCacheProgress,
            FinishedRequestingCache,
        }

        private struct CacheBuilderInfo
        {
            public CacheBuilderInfo(bool rereq, string datadir)
            {
                ReRequest = rereq;
                DataDirectory = datadir;
            }

            public bool ReRequest;
            public string DataDirectory;
        }

        public delegate void IMDbCacheEvent(IMDbEventType et, object data);

        public event IMDbCacheEvent CacheEvent;

        private string dataDirectory = null;

        public IMDbDictionary()
        {
            if (dataDirectory == null)
                dataDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            downloadSync = new ManualResetEvent(false);

            syncFile();
        }

        private void syncFile()
        {
            bool reRequest = false;

            string fullCache = Path.Combine(dataDirectory, plotCacheName);
            if (File.Exists(fullCache))
            {
                DateTime createTime = File.GetCreationTime(fullCache);
                if (DateTime.Now.Subtract(createTime).Days > 5)
                {
                    reRequest = true;
                }
            }
            else
                reRequest = true;

            if (reRequest)
            {
                OnCacheEvent(IMDbEventType.CacheOld, null);
                AlrightJustDownloadIt(dataDirectory);
            }
            else
                downloadSync.Set();
        }

        private void OnCacheEvent(IMDbEventType type, object data)
        {
            if (CacheEvent != null)
            {
                CacheEvent(type, data);
            }
        }

        private void AlrightJustDownloadIt(string dataDir)
        {
            WebClient webClient = new WebClient();
            webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
            webClient.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(webClient_DownloadFileCompleted);

            string cacheName = Path.Combine(dataDir, plotCacheName);

            if (File.Exists(cacheName))
                File.Delete(cacheName);

            OnCacheEvent(IMDbEventType.RequestingCache, DateTime.Now);
            webClient.DownloadFileAsync(new Uri(plotUrl), cacheName);
        }

        void webClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            OnCacheEvent(IMDbEventType.FinishedRequestingCache, DateTime.Now);
            downloadSync.Set();
        }

        void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            _bytesTotal = e.TotalBytesToReceive;
            _bytesRecieved = e.BytesReceived;

            OnCacheEvent(IMDbEventType.RequestCacheProgress, new long[] { _bytesTotal, _bytesRecieved });
        }

        public Dictionary<string, List<IMDbDictionary.Movie>> GetMoviePlotsByTitle(List<string> titles)
        {
            List<FastMovieSearchString> fastSearchTitles = new List<FastMovieSearchString>();
            foreach (string title in titles)
            {
                fastSearchTitles.Add(new FastMovieSearchString(title));
            }

            Dictionary<string, List<Movie>> MovieLookup = new Dictionary<string, List<Movie>>();

            string cacheName = Path.Combine(dataDirectory, plotCacheName);

            /* we must wait for the download of the imdb cache file if its occuring */
            while (!downloadSync.WaitOne(1000))
            {
                Console.Write(string.Format("\rWaiting On Download To Finish... {0}/{1}", _bytesRecieved, _bytesTotal));
                Console.WriteLine();
            }

            using (GZipStream gzipStream = new GZipStream(new FileStream(cacheName, FileMode.Open), CompressionMode.Decompress))
            {
                IMDbRecordReader recordReader = new IMDbRecordReader(new GzipStreamLineReader(gzipStream));

                while (recordReader.AdvanceToNextRecord())
                {
                    foreach (FastMovieSearchString title in fastSearchTitles)
                    {
                        if (recordReader.CompareTitleToCurrentRecord(title))
                        {
                            Movie mov = new Movie();
                            mov.Plot = recordReader.CurrentPlot;
                            mov.Title = recordReader.CurrentTitle;
                            mov.SearchTitle = title.OriginalTitle;

                            List<Movie> matchMovies;
                            if (MovieLookup.TryGetValue(title.OriginalTitle, out matchMovies))
                            {
                                matchMovies.Add(mov);
                            }
                            else
                            {
                                List<Movie> mvs = new List<Movie>();
                                mvs.Add(mov);
                                MovieLookup[title.OriginalTitle] = mvs;
                            }
                        }
                    }
                }
            }

            return MovieLookup;
        }
    }
}
